import socket
import thread
import sys
import time
from GBD import GBD
from netadapter import NetAdapter
from interfaceCli import InterfaceCli
from interfaceServ import InterfaceServ
from clock import Clock


##
# Servidor
# classe que implementa o servidor propriamente dito
class Servidor:

    ##
    # Construtor
    # @param ID identificador do servidor
    def __init__ ( self, ID ):

        self.ID = ID
        print "> script rodando"

        # inicia GBD
        self.bd = GBD("bd"+str(ID)+".txt")
        print "> bd configurado"
        print self.strbdDoServ()

        # abre arquivo
        arq = open("servidor.conf","r")
        # le arquivo
        conf = arq.readlines()
        # fecha arquivo
        arq.close()

        print "> arquivo de configuracao do servidor lido"
        print conf

        # seta lista de servs
        self.aliveServs = 0
        self.servs = []
        self.estoques = []
        for i in range(0, len(conf)):
            self.servs.append(conf[i].split(":"))
            self.estoques.append([False, []]) # False indica unavailable
            
        print "> formada a lista de servidores"
        print self.estoques
        
        # seta relogio logico
        self.ck = Clock()
        print "> relogio logico configurado"
        
        #inicia interfaceServ
        self.intServ = InterfaceServ(self.servs, self.ID, self.ck)         
        print "> Interface servidor configurada"

        # configurar servCliente
        host = self.servs[ID][1]          # Endereco IP do servidor
        port = int(self.servs[ID][2])     # Porta TCP do servidor
        #self.intCli = InterfaceCli(host, port, self.ck)
        self.intCli = InterfaceCli(host, port, self.ck, ID, self.intServ)
        print "> Interface cliente configurada"

        print "> Servidor Pronto!"


    ########################################
    # COMUNICACAO COM CLIENTE              #
    ########################################

    ##
    # Trata a mensagem recebida por um cliente
    # @param msg mensagem a ser tratada
    # @param ck clock da mensagem
    def trataMsg (self, msg, ck):

        msgArray = msg.split(':')
        action = msgArray[0]

        ##
        # ESTIMATE
        if (action == "list"):
            if (len(msgArray) <> 1):
                return "list nao leva argumento"
            return self.listBDToStr(self.listaTotal())
            
        ##
        # QUANTIDADE DE UM PRODUTO
        if (action == "produto"):
            if (len(msgArray) <> 2):
                return "produto leva 1 argumento: idProduto"
            prod = msgArray[1]
            return str(self.qtdeProduto(prod))

        ##
        # COMPRA
        if (action == "sub"):
            if (len(msgArray) <> 3):
                return "produto leva 2 argumentos: idProduto, qtdeProduto"
            prod = msgArray[1]
            qtde = int(msgArray[2])

            ## o naCli deve ser o identificador da msg SUB, ou seja, o ck que ja vem marcado na msg.
            naCli = ck

            # situacao 1: servidor tem quantidade o suficiente
            if self.qtdeProdDoServ(prod, self.ID) >= qtde:

                self.VendeProduto (prod, qtde)

                # broadcast da venda do produto
                ## -> resposta ao cliente nao necessita de relogio
                ## -> e mesmo se precisasse, a interface ja esta gerando o ck!
                msg = "atualizem#"+msg
                self.anunciaServs (msg)
                # alocado:qtde:0
                msgCli='alocado:'+str(qtde)+':0'
                return msgCli
                
            # situacao 2: servidor nao tem mas vizinho sim
            if self.qtdeProduto (prod) >= qtde:
                
                qnt1 = self.qtdeProdDoServ (prod, self.ID)
                qtdtmp = qtde-qnt1

                # aloca recursos proprios para venda
                self.VendeProduto (prod,qnt1)
                # e requisita ao proximo servidor que aloque os demais
                msgServ='preciso#'+str(self.ID)+'#'+str(naCli)+'#'+str(prod)+'#'+str(qtde)+'#'+str(qnt1)+'#0#0'
                self.intServ.enviaMsg(msgServ,(((self.ID)+1)%len(self.servs)))
                
                #resposta verdadeira ao cliente virah no tratamento do 'tenho'
                return 'aguardando'
                
            # situacao 3: ninguem tem
            if self.qtdeProduto (prod) < qtde:
                #####
                # se nao tem, da opcao ao cliente de comprar o que tem
                # alocado:0:qtde
                msgCli = 'alocado:0:'+str(self.qtdeProduto (prod))
                return msgCli

            return
                
        return "cmd invalido"

    ########################################
    # COMUNICACAO COM SERVIDORES           #
    ########################################

    ##
    # aceitaServs
    # inicia thread que vai tratar respostas
    # 
    def aceitaServs (self):
        thread.start_new_thread( self.daemonUDP, tuple() )
        return True

    ##
    # daemonUDP
    # Daemon que recupera mensagem e faz tratamento necessario
    # 
    def daemonUDP (self):
        
        # escuta mensagens
        while True:

            # recebe msg
            (clkVet, msg, servID) = self.intServ.popElemento()
            
            msgArray = msg.split("#")
            action = msgArray[0]
           
            ##
            # Msg do tipo cliente
            # Msg do cliente tem formato Cliente#id#MSG
            if (action == "Cliente"):
                ckClient = msgArray[1]
                msg = ':'.join(msgArray[2:])
                print "> trata msg "+ str(ckClient) +": '" + msg
                resp = self.trataMsg(msg, ckClient)
                if (resp=='aguardando'):
                    continue
                self.intCli.devolveResposta(resp,ckClient)
            
            ##
            # Servidor online
            # formato do msgArray: (((self.ID)+1)%len(self.servs))
            #       0. helloworld
            #       1. bancoDeDadosDoServidor
            if (action == "helloworld"):
                # caso nao tenha sido setado antes
                if (self.estoques[servID][0] == False):
                    self.aliveServs+= 1
                    
                    # atualiza estoque do Servidor
                    lista = self.strBDToList(msgArray[1])
                    self.estoques[servID] = [True, lista]

                    # envia bd como resposta
                    dest = ( str(self.servs[servID][1]) , int(self.servs[servID][3]) )
                    self.intServ.enviaMsg("helloyou#"+self.strbdDoServ(), servID)
                    
                    
            ##
            # Servidor online
            # formato do msgArray:
            #       0. helloyou
            #       1. bancoDeDadosDoServidor
            if (action == "helloyou"):
                # caso nao tenha sido setado antes
                if (self.estoques[servID][0] == False):
                    # atualiza estoque do Servidor
                    lista = self.strBDToList(msgArray[1])
                    self.estoques[servID] = [True, lista]

            ##
            # Atualizar imagem
            # formato do msgArray:
            #       0. atualizem
            #       1. acao:produto:quantidade
            if (action == "atualizem"):
                conteudo = msgArray[1].split(":")
                self.atualizaImg(servID, conteudo[0], conteudo[1], int(conteudo[2]))

            ##
            # Servidor vizinho pede produtos que ele nao tem
            # formato do msgArray:
            #       0. preciso
            #       1. ID do servidor iniciador
            #       2. id do NA que vai responder
            #       3. produto
            #       4. quantidade pedida pelo cliente
            #       5. quantidade alocada pelo Serv iniciador
            #       6. quantidade alocada pelo Serv (iniciador+1)%3
            #       7. quantidade alocada pelo Serv (iniciador+2)%3
            if (action == "preciso"):

                # recupera quantidade disponivel localmente
                qtdeDisponivel = self.qtdeProdDoServ(msgArray[3],self.ID)

                # calcula em qual hop a mensagem "preciso" esta
                remetente = int(msgArray[1])
                if remetente < self.ID:
                    hop = self.ID - remetente
                else:
                    hop = (self.ID+len(self.servs)) - remetente
                # calcula quanto falta
                qtdeFaltante = int(msgArray[4])

                for i in range(0, hop):
                    qtdeFaltante-= int(msgArray[5+i])

                # repassar ou retornar a mensagem?
                debitaBD = True
                if (qtdeFaltante <= qtdeDisponivel):

                    # servidor tem o que o "preciso" pediu: encerra cadeia de "preciso"
                    msgArray[5+hop] = str(qtdeFaltante)
                    msgArray[0] = "tenho"
                    proxDest = ((self.ID)-1) % len(self.servs)

                    # atualiza imagens: SUCESSO na venda
                    for i in range(0, len(self.servs)):
                        if self.ID <> i:
                            self.atualizaImg(i, "replace", msgArray[3], 0)
                    
                else:
                    # servidor nao tem suficiente para atender a demanda
                    msgArray[5+hop] = str(qtdeDisponivel)
                    proxDest = ((self.ID)+1) % len(self.servs)
                    if (hop == len(self.servs)-1):
                        # retornar! -> pois e'o ultimo servidor da cadeia de servs
                        msgArray[0] = "tenho"
                        proxDest = ((self.ID)-1) % len(self.servs)
                        debitaBD = False

                        # atualiza imagens: FALHA na venda
                        for i in range(0, len(self.servs)):
                            if self.ID <> i:
                                idTemp = ( i-int(msgArray[1]) ) % len(self.servs)
                                self.atualizaImg(i, "replace", msgArray[3], int(msgArray[5+idTemp]))

                    else:
                        # repassar!
                        proxDest = ((self.ID)+1) % len(self.servs)

                if debitaBD:
                    # atualiza BD local
                    self.VendeProduto(msgArray[3],int(msgArray[5+hop]))
                 
                # constroi msg resp
                msgResp = "#".join(msgArray)
                # retorna ou repassa msg
                self.intServ.enviaMsg(msgResp, proxDest)
                
            ##
            # TENHO: resposta ao PRECISO
            # formato do msgArray:
            #       0. tenho
            #       1. ID do servidor iniciador
            #       2. id do NA que vai responder
            #       3. produto
            #       4. quantidade pedida pelo cliente
            #       5. quantidade alocada pelo Serv iniciador
            #       6. quantidade alocada pelo Serv (iniciador+1)%3
            #       7. quantidade alocada pelo Serv (iniciador+2)%3
            if (action == "tenho"):
                # calcula em qual hop a mensagem "tenho" esta'
                remetente = int(msgArray[1])
                if remetente < self.ID:
                    hop = (self.ID - remetente) % len(self.servs)
                else:
                    hop = ((self.ID+len(self.servs)) - remetente ) % len(self.servs)

                # verifica se atingiu o total esperado
                qtdePedida = int(msgArray[4])
                soma = 0
                for i in range(5, len(msgArray)):
                    soma+= int(msgArray[i])

                # Se nao atingiu o total:
                if (soma < qtdePedida):
                    # desalocar e restaurar
                    qtdeRestaura = int(msgArray[5+hop])
                    self.restauraProd(msgArray[3], qtdeRestaura)
                    print "> somentes foram alocados ",soma
                    print "> venda falhou!",qtdeRestaura," de ",msgArray[3]," restaurados."
                    # atualiza imagens: FALHA na venda
                    for i in range(0, len(self.servs)):
                        if self.ID <> i:
                            idTemp = ( i-int(msgArray[1]) ) % len(self.servs)
                            self.atualizaImg(i, "replace", msgArray[3], int(msgArray[5+idTemp]))
                            
                else:
                    # atualiza imagens: SUCESSO na venda
                    for i in range(0, len(self.servs)):
                        if self.ID <> i:
                            idTemp = ( i-int(msgArray[1]) ) % len(self.servs)
                            self.atualizaImg(i, "sub", msgArray[3], int(msgArray[5+idTemp]))
                    
                # verifica se a msg chegou ao iniciador
                if (remetente <> self.ID):
                    # retorna ao anterior, caso nao seja o iniciador
                    proxDest = ((self.ID)-1) % len(self.servs)
                    self.intServ.enviaMsg(msg, proxDest)
                    
                else:
                    # constroi msg ao cliente
                    if (soma < qtdePedida):
                        # msg de FALHA na venda
                        resp = 'alocado:0:'+str(soma)
                    else:
                        # msg de SUCESSO na venda
                        resp = 'alocado:'+str(msgArray[4])+':0'
                    # responde ao cliente
                    self.intCli.devolveResposta(resp,msgArray[2])
                
        # finaliza thread
        sys.exit(0)

    ##
    # Anuncia que servidor foi ligado
    # @param msg mensagem a ser enviada a todos os servs
    def anunciaServs (self, msg):
        # envia msg a todos os servs
        for i in range (0, len(self.servs)):
            if (i <> self.ID) and (len(self.servs[i]) > 0):
                self.intServ.enviaMsg(msg,i)
        return True

    ########################################
    # FUNCOES AUXILIARES                   #
    ########################################

    ##
    # strToList
    # Transforma o string de uma lista em lista
    # @param str list em formato string
    def strToList (self, str):
        linhas = str[1:-1]
        lista = linhas.split(",")
        for i in range(0, len(lista)):
            lista[i] = int(lista[i])
        return lista

    ##
    # comparaVet
    # Transforma o string de uma lista em lista
    # @param str list em formato string
    def comparaVet (self, vetA, vetB):
        vetResp = [0,0,0]
        for i in range(0, len(vetA)):
            if (vetA[i] > vetB[i]):
                vetResp[i] = vetA[i]
            else:
                vetResp[i] = vetB[i]
        return vetResp

    ##
    # strBDToList
    # Transforma o string do bd para lista
    # @param strbd bd em formato string
    def strBDToList (self, strbd):
        lista = []
        linhas = strbd.split("\n")
        for i in range (0, len(linhas)):
            if (len(linhas[i]) > 0):
                item = str(linhas[i].split(":")[0])
                qtde = int(linhas[i].split(":")[1])
                linha = [item, qtde]
                lista.append(linha)
        return lista

    ##
    # Transforma a lista do bd para uma string
    # @params listbd bd em formato list
    def listBDToStr (self, listbd):
        string = ""
        for i in range (0, len(listbd)):
            string = string + listbd[i][0] + ":" + str(listbd[i][1]) + "\n"
        return string

    ########################################
    # FUNCOES DE TRATAR MENSAGENS          #
    ########################################
    ##
    # retorna BD local em formato STRING (xxx:111\nyyy:222\n)
    def strbdDoServ (self):
        # recuperar BD local
        return self.bd.listaProduto()
    
    ##
    # BD total, partindo do principio que os itens estao nas mesmas posicoes
    def listaTotal (self):
        # recuperar BD local
        lista = self.strBDToList(self.bd.listaProduto())
        for i in range (0, len(self.estoques)):
            # somar com todas as imagens, exceto ele proprio
            if (i <> self.ID) and (self.estoques[i][0] <> False):
                # recuperar BD do servidor i
                #print self.estoques
                listaServI = self.estoques[i][1]
                #print listaServI
                # somar cada item
                for item in range(0, len(lista)):
                    lista[item][1] = lista[item][1] + listaServI[item][1]
        return lista

    ##
    # quantidade de produtos de um servidor especifico
    def qtdeProdDoServ (self, prod, serv):
        # recuperar qtde local
        if (self.ID == serv):
            return self.bd.quantidadeProduto(prod)

        #recuperar qtde de outro 
        if (self.estoques[serv][0] <> False):
            # recuperar BD do servidor i
            listaServI = self.estoques[serv][1]
            for i in range(0, len(listaServI)):
                if (listaServI[i][0] == prod):
                    resp = listaServI[i][1]
        return resp

    ##
    # Quantidade de um produto, somado todos servidores
    def qtdeProduto (self, prod):
        # recuperar qtde local
        resp = self.bd.quantidadeProduto(prod)
        for i in range (0, len(self.estoques)):
            # somar com todas as imagens, exceto ele proprio
            if (i <> self.ID) and (self.estoques[i][0] <> False):
                # recuperar BD do servidor i
                #print self.estoques
                lista = self.estoques[i][1]
                #print listaServI
                # somar cada item
                for j in range(0, len(lista)):
                    if (lista[j][0] == prod):
                        resp = resp + lista[j][1]
        return resp
 
    ##
    # Atualiza Imagem
    def atualizaImg (self, servId, acao, prod, qtde):
        print "> Imagem ",servId," atualizada com ",acao,":", prod,":", int(qtde)
        lista = self.estoques[servId][1]
        for i in range(0, len(lista)):
            if (lista[i][0] == prod):
                print "< ",lista[i][1]
                if (acao == "sub"):
                    lista[i][1]-= int(qtde)
                if (acao == "replace"):
                    lista[i][1] = int(qtde)
                print "> ",lista[i][1]
                
    ##
    # Restaura BD (caso de falha de venda)
    def restauraProd(self, prod, qtde):
        self.bd.aumentaProduto(prod, int(qtde))
             
    ########################################
    # VENDA                                #
    ########################################

    def VendeProduto(self, idProd, qtdProd):
        # atualiza estoque
        self.bd.diminuiProduto(idProd, qtdProd)
        return        

    def trataPedidoFimCompra():
        # para cada produto:
        ## recebe mensagem de sub de finalizaCompra
        ## envia quantidade para o cliente  
        ## recebe mensagem de sub de trataRespostaCompra
        ## self.VendeProduto ()
        pass
        

# fim class servidor


# config server
servID = sys.argv[1]
serv = Servidor(int(servID))

# start daemon UDP
serv.aceitaServs()

# anuncia serv vivo E
# envia bd
    # recupera proprio bd
strbd = serv.bd.listaProduto()
    # constroi msg
msg = "helloworld#"+strbd
serv.anunciaServs( msg )

# conexoes TCP dos clientes
while True:
    pass
